package com.s.remotelog.rpc;

import android.content.Context;
import android.os.SystemClock;
import android.util.Log;

import com.s.remotelog.wifi.WifiOpImpl;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;

import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import s.log.Code;
import s.log.Level;
import s.log.Message;
import s.log.RPCLogGrpc;
import s.log.Response;

public class RPCClient {

    static {
        System.loadLibrary("rlog");
    }

    private static final String TAG = "RLog";
    private static final int MAX_TRY_TIMES = 5;
    private static final int MAX_LOG_ITEMS = 500;

    private static RPCClient sInstance = new RPCClient();
    private BlockingQueue<RPCEvent> mEvents = new LinkedBlockingQueue<>(MAX_LOG_ITEMS);
    private ManagedChannel mChannel;
    private ClientStub mStub;
    private boolean mExit = false;
    private boolean mStarted = false;
    private int mMaxLogItems = MAX_LOG_ITEMS;

    public static RPCClient getInstance() {
        return sInstance;
    }

    public void setMaxLogCache(int max) {
        mMaxLogItems = max;
        mEvents = new PriorityBlockingQueue<>(max);
    }

    public void start(final Context context, final String ssid, final String pwd) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                mStarted = true;
                for (;;) {
                    Log.i(TAG, "will enable rpc ");
                    mStub = enableRPCClient(context, ssid, pwd);
                    if (mExit) {
                        clear();
                        break;
                    }
                    Log.i(TAG, "rpc enabled .... ");
                    if (!isChannelAlive() || mStub == null) {
                        sleepFor(5000, "Create stub failed !");
                        continue;
                    }

                    if (!connect(mStub)) {
                        sleepFor(5000, "Connect to server failed !");
                        continue;
                    }
                    Log.i(TAG, "rpc connected .... ");
                    reporting(mStub);

                    if (mExit) {
                        clear();
                        break;
                    }
                }
                mStarted = false;
            }
        }, "rpc_transfer").start();

        NativeLogTransfer.getInstance().registerNativeLogCallback(new INativeLogCallback() {
            @Override
            public void onNativeLog(int level, String tag, String msg) {
                Log.i(TAG, "C++___dispatch : " + level + " tag, " + tag + " msg : " + msg);
                dispatchEvent(RPCEvent.make(level, tag, msg));
            }
        });
    }

    public void stop() {
        mExit = true;
        dispatchEvent(RPCEvent.make(Level.I_VALUE, null, null));
    }

    public boolean isStarted() {
        return mStarted;
    }

    private void clear() {
        mEvents.clear();
    }

    private ClientStub enableRPCClient(Context context, String ssid, String pwd) {
        WifiOpImpl wifiOp = new WifiOpImpl(context);
        boolean conn = false;
        if (wifiOp.enableWifi()) {
            conn = wifiOp.connWifiSync(ssid, pwd, "WPA", 150);
        }
        if (!conn) {
            Log.e(TAG, "connect failed !");
            return null;
        }

        while (!wifiOp.isWifiConn());

        ClientStub stub = new ClientStub();

        try {
            String ip = wifiOp.getApHostIp();
            mChannel = ManagedChannelBuilder.forAddress(ip, 12110)
                    .maxInboundMessageSize(20 * 1024 * 1024)
                    .usePlaintext(true)
                    .build();
            if (!isChannelAlive()) {
                return null;
            }

            stub.blockingStub = RPCLogGrpc.newBlockingStub(mChannel);
            stub.asyncStub = RPCLogGrpc.newStub(mChannel);

        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return stub;
    }


    private boolean connect(ClientStub stub) {
        try {
            Message msg = Message.newBuilder()
                    .setLevel(Level.I_VALUE)
                    .setTag("RLog")
                    .setMsg("RPC Log Connected")
                    .build();
            int tryTimes = 0;
            while (++tryTimes <= MAX_TRY_TIMES) {
                Log.i(TAG, "will report message .... ");
                Response resp = stub.blockingStub.report(msg);
                Log.i(TAG, "resp : " + resp.getCode());
                if (resp.getCode() == Code.EN__EN___VALUE) {
                    return true;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            Log.e(TAG, "send message failed ! : " + e.getMessage());
        }
        return false;
    }

    private void reporting(ClientStub stub) {
        for (;;) {
            try {
                RPCEvent event = mEvents.take();
                if (mExit) {
                    break;
                }
                sendEvent(stub, event);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private void sendEvent(ClientStub stub, final RPCEvent event) {
        try {
            Response resp = stub.blockingStub.report(event.getMsg());
            if (resp != null && resp.getCode() != Code.EN__EN___VALUE) {
                tryResentEvent(event);
            }
        } catch (Exception e) {
            e.printStackTrace();
            tryResentEvent(event);
        }
    }

    private void tryResentEvent(RPCEvent event) {
        if (event.getIndex() <= 5) {
            event.setIndex(event.getIndex() + 1);
            dispatchEvent(event);
        }
    }

    public void dispatchEvent(RPCEvent event) {
        try {
            if (mEvents.size() >= mMaxLogItems) {
                mEvents.take();
            }
            mEvents.offer(event);
            Log.i(TAG, "insert event : " + event.toString());
        } catch (Exception e) {
            e.printStackTrace();
            Log.e(TAG, "dispatch event failed : " + e.getMessage());
        }
    }

    private boolean isChannelAlive() {
        return mChannel != null && !mChannel.isShutdown() && !mChannel.isTerminated();
    }

    private void sleepFor(long ms, String reason) {
        Log.e(TAG, reason);
        SystemClock.sleep(ms);
    }

}
